#  apk加固工具
# apktool.jar进行解包，替换lib目录下的so文件
# AndResGuard.jar 进行资源混淆
# 并打印出apk中resources.arsc文件和r目录下所有文件的crc签名
#  1、资源压缩、混淆
#  2、动态向cpp代码中加入apk中文件的CRC签名，C++编译，生成so，并替换或者添加到apk中。
#  3、v1+v2+v3签名
import os
import shutil
import threading
import time
import tools
import FileTools

apkPath_teacher = "/Users/xuyan/Desktop/eng100-teacher-apk/"
# apkPath_student = "/Users/xuyan/Desktop/testApk/"
apkPath_student = "/Users/xuyan/Desktop/eng100-student-apk/"

dirPath = "./"
#工具目录
bin = dirPath + "bin/"
student_sign = bin+"eng100.jks"
teacher_sign = bin+"eng100_teacher.jks"
#解压与压缩apk工具
apktool = bin+"apktool_2.5.0.jar"
#动态库替换目录
lib_so = bin + "so_lib/"
# NDK目录
ndk_path = "/Users/xuyan/Library/Android/sdk/ndk/21.3.6528147/ndk-build"
#安全CPP代码的根路径
ndk_cpp_path = bin+"cpp/"
ndk_cpp_libs = ndk_cpp_path+"libs/"
ndk_cpp_code_path = ndk_cpp_path+"appSecurity.cpp"
#资源压缩工具
and_res_guard = bin+"AndResGuard.jar"
stu_res_config = bin +"config-student.xml"
teacher_res_config = bin +"config-teacher.xml"
#apk签名
apksigner = bin+"apksigner"
#zip压缩工具
zip7za_path = bin+"p7zip/16.02_2/bin/7za"
#资源对齐工具
zipalign_path = bin+"zipalign"
#学生端签名文件
eng100Jks = bin+"eng100.jks"
#教师端签名文件
eng100TeacherJks = bin+"eng100_teacher.jks"
#工作目录
work_path = bin+"work/"
work_path_res = work_path+"resource/"#压缩资源目录
workType = 0 #学生端：0，教师端：1
#是否生成加固组件
isGenerateSecurity = False
threads = []

#
# zipAlign对so进行4字节对齐
#
def zipAlignApk(threadId,apk,work_path_res_child,orApk):
    apkPath = os.path.dirname(apk)
    outApk = apkPath+"/app-apk"+ "-aligned.apk"
    command_line = zipalign_path+" -v -f 4 "+apk+" "+outApk
    tools.tryExecuteCommand(command_line, onZipAlignApkCallbackFunc,threadId,apk, outApk,work_path_res_child,orApk)

def onZipAlignApkCallbackFunc(type,params):
    threadId = params[0]
    res_apk_path = params[1]
    apkPath = params[2]
    work_path_res_child = params[3]
    orApk = params[4]
    if type == 0:
        print("字节对齐成功")
        # 拷贝文件cpp目录
        work_path_res_child = os.path.dirname(os.path.dirname(work_path_res_child))
        ndk_cpp_path_child = os.path.join( work_path_res_child,"cpp")
        if not os.path.exists(ndk_cpp_path_child):
            FileTools.mkDir(ndk_cpp_path_child)
        FileTools.copy_file(ndk_cpp_path, ndk_cpp_path_child)
        # 计算CRC
        canculateApkRCR(threadId, work_path_res_child, ndk_cpp_path_child, apkPath, orApk)
    else:
        print(apkPath + "字节对齐失败")

#apk资源压缩成功
def onConfusionResourceCallbackFunc(type,params):
    threadId = params[0]
    orApk = params[1]
    work_path_res_child = params[2]
    if type == 0:
        print("资源压缩成功")
        dir = FileTools.readDir(work_path_res_child)
        apk = orApk[orApk.rfind("/")+1:len(orApk)-len(".apk")]
        for r in dir:
            if r.startswith(apk+"_signed_aligned"):
                apkPath = work_path_res_child+"/"+r

                #计算CRC
                # canculateApkRCR(threadId,work_path_res_child,ndk_cpp_path_child,apkPath,orApk)
                zipAlignApk(threadId, apkPath,work_path_res_child,orApk)
                break
    else:
        print("资源压缩失败")
    # if FileTools.dirIsExists(work_path_res_child):
    #     FileTools.delFile(work_path_res_child)

# 混淆资源文件
# java -jar andresguard.jar -repackage input.apk -out output_directory
#  -7zip /usr/local/bin/7za -zipalign /Users/xuyan/Library/Android/sdk/build-tools/29.0.3/zipalign
def confusionResource(threadId,apk):
    res_config = stu_res_config
    apkName = apk[apk.rfind("/")+1:(len(apk)-len(".apk"))]
    work_path_res_child = work_path_res + apkName+"/apk"
    if not FileTools.dirIsExists(work_path_res_child):
        FileTools.mkDir(work_path_res_child)
    if workType == 1:
        res_config = teacher_res_config
    command_line = "java -jar " + and_res_guard \
                   + " "+apk+ " -config " + res_config + " -out " + work_path_res_child\
                   + " -7zip "+zip7za_path+" -zipalign "+zipalign_path

    print("压缩资源的命令："+command_line)
    tools.tryExecuteCommand(command_line, onConfusionResourceCallbackFunc,threadId,apk,work_path_res_child)

#解压apk回调
def unZipApkCallbackFunc(type,params):
    threadId = params[0]
    apk = params[1]
    work_path_res_child = params[2]
    ndk_cpp_path_child = params[3]
    orApk = params[4]
    if type == 0:
        print("解压成功")
        apkName = orApk[orApk.rfind("/") + 1:(len(orApk) - len(".apk"))]
        # apkDirPath = target.replace(".apk","")
        # path = apkDirPath + "/lib/"
        # apkDirPath = os.path.join(work_path_res_child,apkName)
        apkDirPath = os.path.join(work_path_res_child,"apk/app/"+apkName)
        path = os.path.join(apkDirPath,"lib")
        # toPath = dirPath+apkDirPath[apkDirPath.rfind("/")+1:len(apkDirPath)]
        replaceLibSo(threadId,path,ndk_cpp_path_child,work_path_res_child)
    else:
        print("解压失败")

# 解压APK
def unzipApk(threadId,apk,work_path_res_child,ndk_cpp_path_child,orApk):
    apkName = orApk[orApk.rfind("/") + 1:(len(orApk) - len(".apk"))]
    command_line = "java -jar "+apktool+" d "+orApk+" -o "+work_path_res_child+"/apk/app/"+apkName
    tools.tryExecuteCommand(command_line,unZipApkCallbackFunc,threadId,apk,work_path_res_child,ndk_cpp_path_child,orApk)

def onZipApkCallbackFunc(type,params):
    threadId = params[0]
    apk = params[1]
    if type == 0:
        print("压缩成功")
        # zipAlignApk(threadId,apk)
        #资源压缩
        confusionResourceNext(threadId,apk)
    else:
        print("压缩失败")

# apk打包
def zipApk(threadId,path,work_path_res_child):
    print("开始打包")
    apk = os.path.dirname(path)+"/"+os.path.basename(path)+".apk"
    command_line = "java -jar " + apktool + " b " + path + " -o "+apk
    tools.tryExecuteCommand(command_line, onZipApkCallbackFunc,threadId,apk)

def onSignedCallbackFunc(type,params):
    threadId = params[0]
    apkPath = params[1]
    # if FileTools.dirIsExists(apkPath):
    #     FileTools.delFile(apkPath)
    if type == 0:
        print(apkPath+"签名成功")
    else:
        print(apkPath+"签名失败")

#
# zipAlign对so进行4字节对齐
#
def zipAlignApkNext(threadId,apk):
    apkPath = os.path.dirname(apk)
    outApk = apkPath+"/app"+ "-aligned.apk"
    command_line = zipalign_path+" -v -f 4 "+apk+" "+outApk
    tools.tryExecuteCommand(command_line, onZipAlignApkNextCallbackFunc,threadId,apk, outApk)

def onZipAlignApkNextCallbackFunc(type,params):
    threadId = params[0]
    res_apk_path = params[1]
    apkPath = params[2]
    if type == 0:
        print("字节对齐成功")
        signedApk(threadId,apkPath)
    else:
        print(apkPath + "字节对齐失败")

# /Users/xuyan/Library/Android/sdk/build-tools/29.0.3/apksigner  sign --ks eng100_teacher.jks --ks-key-alias eng100 --out eng100-teacher-tencent.apk eng100-teacher-1.0.4-Tencent-release_signed.apk
# v1+v2+v3签名
def signedApk(threadId,apk):
    signJks = eng100Jks
    path = "student/"
    if workType == 1:
        signJks = eng100TeacherJks
        path = "teacher/"
    apkName = apk[apk.rfind("/")+1:(len(apk)-len(".apk"))]
    out_apk = work_path + path
    if not FileTools.dirIsExists(out_apk):
        FileTools.mkDir(out_apk)
    out_apk = out_apk + apkName+"-signed.apk"
    command_line = apksigner+" sign --ks " + signJks  \
                   + " --ks-key-alias eng100 --ks-pass pass:wanhe100 --key-pass pass:wanhe100 --out " + out_apk +" "+apk
    print(command_line)
    tools.tryExecuteCommand(command_line, onSignedCallbackFunc,threadId,apk)

#计算CRC签名，并修改appSecurity.cpp,然后生成so库
def canculateApkRCR(threadId,work_path_res_child,ndk_cpp_path_child,apk,orApk):
    rPathCRC = FileTools.canculateCRC(apk, "r/")
    resourcesCRC = FileTools.canculateCRC(apk, "resources.arsc")
    dexCRC = FileTools.canculateCRC(apk, ".dex")
    #动态更改appSecurity.cpp文件中的apkResourceCRC、rFileCRC、dexCRC值
    file = ndk_cpp_path_child+"/appSecurity.cpp"
    output = []
    with open(file, "+r", encoding="utf-8")as f:
        for line in f.readlines():
            if "*apkResourceCRC" in line:
                print(line)
                line = os.path.join('const char *apkResourceCRC = "',resourcesCRC,'";\n')
                line = line.replace("/", "")
            elif "*rFileCRC" in line:
                print(line)
                line = os.path.join('const char *rFileCRC = "' , rPathCRC , '";\n')
                line = line.replace("/", "")
            elif "*dexCRC" in line:
                print(line)
                line = os.path.join('const char *dexCRC = "' , dexCRC , '";\n')
                line = line.replace("/","")
            output.append(line)
        f.close()
    with open(file, "w", encoding="utf-8")as wf:
        wf.writelines(output)
        wf.close()
    compileSo(threadId,work_path_res_child,ndk_cpp_path_child,apk,orApk)

# 替换so文件
def replaceLibSo(threadId,path,ndk_cpp_path_child,work_path_res_child):
    ndk_cpp_libs_path_child = os.path.join(ndk_cpp_path_child, "libs")
    fileList = FileTools.readDir(ndk_cpp_libs_path_child)
    print("替换so文件：")
    for f in fileList:
        lib_path = ndk_cpp_libs_path_child+"/" +f+"/"
        for f_dir in FileTools.readDir(lib_path):
            os_path = lib_path + f_dir
            lib_os_path = path+"/lib/" + f + "/" + f_dir
            if FileTools.dirIsExists(lib_os_path):#只拷贝apk中存在的so
                print("%s ===> %s" % (os_path, path + f))
                shutil.copyfile(os_path,lib_os_path)
    apkPath = path.replace("/lib","")
    zipApk(threadId,apkPath,work_path_res_child)

# cpp_so_lib = ndk_cpp_libs
def onCompileSoCallbackFunc(type,params):
    threadId = params[0]
    apk = params[1]
    work_path_res_child = params[2]
    ndk_cpp_path_child = params[3]
    orApk = params[4]
    if type == 0:
        print("so生成成功")
        #FileTools.delFile(target)
        unzipApk(threadId,apk,work_path_res_child,ndk_cpp_path_child,orApk)
    else:
        print("so生成失败")

# 编译so文件
def compileSo(threadId,work_path_res_child,ndk_cpp_path_child,apk,orApk):
    #cpp copy to work_path_res_child
    # ndk_cpp_path_child = os.path.join(work_path_res_child,"cpp")
    # if not os.path.exists(ndk_cpp_path_child):
    #     FileTools.mkDir(ndk_cpp_path_child)
    # FileTools.copy_file(ndk_cpp_path,ndk_cpp_path_child)
    androidMK = ndk_cpp_path_child+"/Android.mk"
    applicationMK = ndk_cpp_path_child+"/Application.mk"
    command_line = ndk_path+" NDK_PROJECT_PATH="+ndk_cpp_path_child+" APP_BUILD_SCRIPT="+androidMK+" NDK_APPLICATION_MK="+applicationMK
    print(command_line)
    tools.tryExecuteCommand(command_line,onCompileSoCallbackFunc,threadId,apk,work_path_res_child,ndk_cpp_path_child,orApk)

def onConfusionResourceNextCallbackFunc(type,params):
    threadId = params[0]
    orApk = params[1]
    work_path_res_child = params[2]
    if type == 0:
        print("资源压缩成功")
        dir = FileTools.readDir(work_path_res_child)
        apk = orApk[orApk.rfind("/")+1:len(orApk)-len(".apk")]
        for r in dir:
            if r.startswith(apk+"_signed_aligned"):
                apkPath = work_path_res_child+"/"+r
                zipAlignApkNext(threadId,apkPath)
                break
    else:
        print("资源压缩失败")
    # if FileTools.dirIsExists(work_path_res_child):
    #     FileTools.delFile(work_path_res_child)

def confusionResourceNext(threadId,apk):
    res_config = stu_res_config
    apkName = apk[apk.rfind("/")+1:(len(apk)-len(".apk"))]
    work_path_res_child = work_path_res + apkName+"/apk1"
    if not FileTools.dirIsExists(work_path_res_child):
        FileTools.mkDir(work_path_res_child)
    if workType == 1:
        res_config = teacher_res_config
    command_line = "java -jar " + and_res_guard \
                   + " "+apk+ " -config " + res_config + " -out " + work_path_res_child\
                   + " -7zip "+zip7za_path+" -zipalign "+zipalign_path

    print("压缩资源的命令："+command_line)
    tools.tryExecuteCommand(command_line, onConfusionResourceNextCallbackFunc,threadId,apk,work_path_res_child)

#
# 调度apk加固线程
#
def schedulingApk(i,apk):
    t = threading.Thread(target=confusionResource,args=(i,apk))
    threads.append(t)
    t.start()


if __name__ == '__main__':
    apk = "/Users/xuyan/PythonProject/apkTools/bin/work/resource/eng100-student-1.2.18-Wanhe-release/apk1/eng100-student-1.2.18-Wanhe-release_signed.apk"
    print(os.path.basename(apk))
    print(os.path.dirname(apk))
    print(os.path.join(os.path.dirname(apk),"appp.apk"))
    # rPathCRC = FileTools.canculateCRC(apk, "r/")
    # resourcesCRC = FileTools.canculateCRC(apk, "resources.arsc")
    # dexCRC = FileTools.canculateCRC(apk, ".dex")
    # print(rPathCRC)
    # print(resourcesCRC)
    # print(dexCRC)

    # r /: 3854085747750
    # resources.arsc: 1977092034
    # .dex: 5864342744


    # apkPath = apkPath_student
    # if workType == 1:
    #     apkPath = apkPath_teacher
    # apkList = FileTools.readFile(apkPath)
    # FileTools.del_dir_in_files(ndk_cpp_libs)
    # for i,apk in enumerate(apkList):
    #     schedulingApk(i,apk)


